
import multiprocessing as mp
import threading
import inspect
import functools
import traceback

_SYNC_LOCK = mp.Lock()

class ThreadResult:
    def __init__(self):
        self.thread     = None
        self.result     = None
        self.exception  = None

    @property
    def isDone(self):
        return not self.thread.isAlive()

    @property
    def hasErrors(self):
        return self.exception is not None

    def getResult(self):
        if not self.isDone:
            self.thread.join()

        if self.hasErrors:
            raise self.exception

        return self.result

def thread(proc=None, daemon=False):
    if proc is None:
        def daemon_thread(proc):
            return thread(proc, daemon)
        return daemon_thread

    @functools.wraps(proc)
    def new_proc(*args, **kw):
        res = ThreadResult()
        def job_proc(*args, **kw):
            try:
                res.result = proc(*args, **kw)
            except Exception as e:
                traceback.print_exc()
                res.exception = e

        t = threading.Thread(
            target = job_proc,
            name   = proc.__name__,
            args   = args,
            kwargs = kw)
        t.daemon = daemon

        res.thread = t
        t.start()
        return res
    return new_proc

def _get_obj_lock(obj):
    try:
        return obj._sync_lock
    except AttributeError:
        pass

    with _SYNC_LOCK:
        try:
            return obj._sync_lock
        except AttributeError:
            pass

        obj._sync_lock = lock = threading.RLock()
        return lock

class _SyncWrapper:
    __slots__ = ("_sync_lock", "_proc")

    def __init__(self, proc):
        self._proc = proc

    def __get__(self, obj, type=None):
        if obj is None:
            def clsFunc(obj, *args, **kw):
                lock = _get_obj_lock(obj)
                with lock:
                    return self._proc(obj, *args, **kw)
            return clsFunc

        lock = _get_obj_lock(obj)
        def func(*args, **kw):
            with lock:
                return self._proc(obj, *args, **kw)
        return func

    def __call__(self, *args, **kw):
        lock = _get_obj_lock(self)
        with lock:
            return self._proc(*args, **kw)

def _make_sync_class(cls):
    assert not hasattr(cls, "__enter__")
    assert not hasattr(cls, "__exit__")

    def __enter__(self):
        lock = _get_obj_lock(self)
        lock.acquire()
    cls.__enter__ = __enter__

    def __exit__(self, exc_type, exc_val, tb):
        lock = _get_obj_lock(self)
        lock.release()
    cls.__exit__ = __exit__
    return cls

def synchronized(proc_or_cls):
    if inspect.isclass(proc_or_cls):
        return _make_sync_class(proc_or_cls)
    return _SyncWrapper(proc_or_cls)

def synchronized_all(cls):
    _make_sync_class(cls)

    for prop in dir(cls):
        if prop.startswith('_'):
            continue

        try:
            propVal = getattr(cls, prop)
        except AttributeError:
            continue

        if not inspect.isroutine(propVal) or isinstance(propVal, _SyncWrapper):
            continue

        setattr(cls, prop, _SyncWrapper(propVal))

    return cls
